#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_system.h"
#include "esp_http_client.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mbedtls/base64.h"
#include "mbedtls/md.h"
#include "http_header.h"
#include "esp_crt_bundle.h"
#include "esp_tls.h"
#include "geminai_oss.h"
//存储ALIYUN errno错误消息的数组的长度
#define AOS_ERRNO_MSG_MAX 64
//哈希运算消息认证码长度
#define HMAC_LEN 20
//要进行签名的字段长度
#define STR_TO_SIGN_LEN 1024
//带有access_key_id 的authorization字段的长度
#define AUTHORIZATION_LEN 256

#define OSS_URL_LEN 256
#define OSS_DIR_LEN 128

#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_OUTPUT_BUFFER 2048
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#ifndef ARRAY_SIZE
# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif

static const char *TAG = "GEMINAI_OSS";

char *aos_strerror(int err_num, char *buf, size_t buflen)
{

    int rv;

    /* POSIX version available for example on FreeBSD or in musl libc */
    rv = strerror_r(err_num, buf, buflen);
    /* make sure the buffer is initialized and terminated with NULL */
    if (-rv == ERANGE) {
        buf[0] = '\0';
    }
    return buf;
}
static char *base64_encode(const unsigned char *input, int length) {
    esp_err_t ret = ESP_OK;
    size_t output_length = 0;
    int encoded_len=0;
    char err_msg[AOS_ERRNO_MSG_MAX] = {0};

    encoded_len = 4 * ((length + 2) / 3) + 1;
    char *encoded = (char *)malloc(encoded_len);
    if (encoded == NULL) {
        ESP_LOGE(TAG, "%s(%d) malloc return NULL %s", __FILE__, __LINE__,
                  aos_strerror(errno, err_msg, AOS_ERRNO_MSG_MAX));
        return NULL;
    }
    memset(encoded, '\0', sizeof(encoded_len));

    ret = mbedtls_base64_encode((unsigned char *)encoded, encoded_len, &output_length, input, length);
    if (ret != 0) {
        ESP_LOGE(TAG, "%s(%d) mbedtls_base64_encode error %s", __FILE__, __LINE__, esp_err_to_name(ret));
        free(encoded);
        return NULL;
    }

    ESP_LOGI(TAG, "encoded:[%s]", encoded);
    return encoded;
}

static char *generate_signature(const char *string_to_sign, const char *access_securty) {
    unsigned char hmac_result[HMAC_LEN] = {0};
    mbedtls_md_context_t ctx;
    mbedtls_md_type_t md_type = MBEDTLS_MD_SHA1;
    const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(md_type);

    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, md_info, 1);
    mbedtls_md_hmac_starts(&ctx, (const unsigned char *)access_securty, strlen(access_securty));
    mbedtls_md_hmac_update(&ctx, (const unsigned char *)string_to_sign, strlen(string_to_sign));
    mbedtls_md_hmac_finish(&ctx, hmac_result);
    mbedtls_md_free(&ctx);

    return base64_encode(hmac_result, sizeof(hmac_result));
}

static char *generate_authorization_upload(const char *date, 
        const char *oss_path, 
        const char *oss_endpoint, 
        const char *bucket_name, 
        const char *access_key, 
        const char *access_securty,
        const char *sts_token) 
{
    char *signature = NULL;
    char *authorization = NULL;
    char string_to_sign[STR_TO_SIGN_LEN]= {0};
    char err_msg[AOS_ERRNO_MSG_MAX] = {0};

    int ret_snprintf = 0;

    if(sts_token != NULL)
    {
        ret_snprintf = snprintf(string_to_sign, sizeof(string_to_sign), "PUT\n\napplication/octet-stream\n%s\nx-oss-security-token:%s\n/%s/%s",
             date, sts_token, bucket_name, oss_path);
    }
    else
    {
        ret_snprintf = snprintf(string_to_sign, sizeof(string_to_sign), "PUT\n\napplication/octet-stream\n%s\n/%s/%s",
             date, bucket_name, oss_path);
    }


    if (ret_snprintf < 0 || ret_snprintf >= sizeof(string_to_sign)) {
        ESP_LOGE(TAG, "%s(%d) snprintf error or string truncated", __FILE__, __LINE__);
        return NULL;
    }

    ESP_LOGI(TAG, "string_to_sign:[%s]", string_to_sign);

    signature = generate_signature(string_to_sign, access_securty);
    authorization = (char *)malloc(AUTHORIZATION_LEN);
    if(authorization == NULL)
    {
        ESP_LOGE(TAG, "%s(%d) malloc return NULL %s", __FILE__, __LINE__,
                  aos_strerror(errno, err_msg, AOS_ERRNO_MSG_MAX));
        free(signature);
        return NULL;        
    }
	memset(authorization, '\0', AUTHORIZATION_LEN);
    snprintf(authorization, AUTHORIZATION_LEN, "OSS %s:%s", access_key, signature);
    ESP_LOGI(TAG, "authorization:[%s]", authorization);
    free(signature);
    return authorization;
}

static char *generate_authorization_download(const char *date, 
        const char *oss_path, 
        const char *oss_endpoint, 
        const char *bucket_name, 
        const char *access_key, 
        const char *access_securty,
        const char *sts_token) 
{
    char *signature = NULL;
    char *authorization = NULL;
    char string_to_sign[STR_TO_SIGN_LEN]= {0};
    char err_msg[AOS_ERRNO_MSG_MAX] = {0};

    int ret_snprintf = 0;

    if(sts_token != NULL)
    {
        ret_snprintf = snprintf(string_to_sign, sizeof(string_to_sign), "GET\n\n\n%s\nx-oss-security-token:%s\n/%s/%s",
             date, sts_token, bucket_name, oss_path);
    }
    else
    {
        ret_snprintf = snprintf(string_to_sign, sizeof(string_to_sign), "GET\n\n\n%s\n/%s/%s",
             date, bucket_name, oss_path);
    }


    if (ret_snprintf < 0 || ret_snprintf >= sizeof(string_to_sign)) {
        ESP_LOGE(TAG, "%s(%d) snprintf error or string truncated", __FILE__, __LINE__);
        return NULL;
    }

    ESP_LOGI(TAG, "string_to_sign:[%s]", string_to_sign);

    signature = generate_signature(string_to_sign, access_securty);
    authorization = (char *)malloc(AUTHORIZATION_LEN);
    if(authorization == NULL)
    {
        ESP_LOGE(TAG, "%s(%d) malloc return NULL %s", __FILE__, __LINE__,
                  aos_strerror(errno, err_msg, AOS_ERRNO_MSG_MAX));
        free(signature);
        return NULL;        
    }
    memset(authorization, '\0', AUTHORIZATION_LEN);
    snprintf(authorization, AUTHORIZATION_LEN, "OSS %s:%s", access_key, signature);
    ESP_LOGI(TAG, "authorization:[%s]", authorization);
    free(signature);
    return authorization;
}
esp_err_t oss_upload_mem(const char *file_name, 
                const char *data, 
                const long  data_size,
                const char *oss_endpoint, 
                const char *bucket_name,
                const char *bucket_dir, 
                const char *access_key, 
                const char *access_securty,
                const char *sts_token)
{
    esp_err_t ret = ESP_OK;
    esp_http_client_handle_t client;
    char oss_url[OSS_URL_LEN] = {0};
    char gmt_time[64] = {0};
    char oss_path[OSS_DIR_LEN] = {0};
    char err_msg[AOS_ERRNO_MSG_MAX] = {0};
    long file_size=0L;

    if(data == NULL || file_name == NULL || data_size == 0)
        return -1;

    file_size = data_size;

    const char *filename = file_name;

    if(bucket_dir!=NULL)
    {
        snprintf(oss_path,sizeof(oss_path),"%s/%s",bucket_dir, filename);
    }
    else
    {
        memcpy(oss_path,filename,sizeof(oss_path));
    }

    snprintf(oss_url, sizeof(oss_url), "https://%s.%s/%s",bucket_name, oss_endpoint, oss_path);
    ESP_LOGI(TAG,  "oss_endpoint:[%s]", oss_endpoint);
    ESP_LOGI(TAG, "oss_url :[%s]", oss_url);

    time_t now = time(NULL);
    struct tm *tm_info = gmtime(&now);
    strftime(gmt_time, sizeof(gmt_time), "%a, %d %b %Y %H:%M:%S GMT", tm_info);

    char *authorization = generate_authorization_upload(gmt_time, oss_path, oss_endpoint, bucket_name, access_key, access_securty,NULL);
    if(authorization == NULL)
    {
        goto generate_authorization_failed;
    }

    esp_http_client_config_t config = {
        .url = oss_url,
        .method = HTTP_METHOD_PUT,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .event_handler = NULL,
    };

    client = esp_http_client_init(&config);
    if(client == NULL)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_init error", __FILE__, __LINE__);
        goto client_init_failed;
    }

    esp_log_level_set("HTTP_CLIENT", ESP_LOG_VERBOSE);
    ret=esp_http_client_set_header(client, "Content-Type", "application/octet-stream");
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }

    ret=esp_http_client_set_header(client, "Date", gmt_time);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }


    ret=esp_http_client_set_header(client, "Authorization", authorization);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }

    if (sts_token != NULL) {

        ret = esp_http_client_set_header(client, "x-oss-security-token", sts_token);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
            goto buffer_alloc_error;
        }
    }


	char *file_data = data;

    ret=esp_http_client_set_post_field(client, file_data, file_size);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) mbedtls_base64_encode error %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }   

    ret = esp_http_client_perform(client);
    ESP_LOGI(TAG, "HTTP Status = %d, content_length = %d",
                     (int)esp_http_client_get_status_code(client),
                     (int)esp_http_client_get_content_length(client));
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "%s(%d) HTTP PUT failed: %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }

	// free(file_data);


buffer_alloc_error:
    esp_http_client_cleanup(client);    
client_init_failed:
    free(authorization);
generate_authorization_failed:
    return ret;
}

esp_err_t oss_upload(const char *file_path, 
                const char *oss_endpoint, 
                const char *bucket_name,
                const char *bucket_dir, 
                const char *access_key, 
                const char *access_securty,
                const char *sts_token)
{
    esp_err_t ret = ESP_OK;
    esp_http_client_handle_t client;
    char oss_url[OSS_URL_LEN] = {0};
    char gmt_time[64] = {0};
    char oss_path[OSS_DIR_LEN] = {0};
    char err_msg[AOS_ERRNO_MSG_MAX] = {0};
    FILE *file = NULL;
    long file_size=0L;


	file= fopen(file_path, "rb");
    if (!file) {
       	ESP_LOGE(TAG, "%s(%d) fopen error %s", __FILE__, __LINE__,
                      aos_strerror(errno, err_msg, AOS_ERRNO_MSG_MAX));
     	return ESP_FAIL;
    }

    
    fseek(file, 0, SEEK_END);
    file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    const char *filename = strrchr(file_path, '/');
    if (filename) {
        filename = filename + 1;
    } else {
        filename = file_path;
    }
    
    if(bucket_dir!=NULL)
    {
        snprintf(oss_path,sizeof(oss_path),"%s/%s",bucket_dir, filename);
    }
    else
    {
        memcpy(oss_path,filename,sizeof(oss_path));
    }

    snprintf(oss_url, sizeof(oss_url), "https://%s.%s/%s",bucket_name, oss_endpoint, oss_path);
    ESP_LOGI(TAG,  "oss_endpoint:[%s]", oss_endpoint);
    ESP_LOGI(TAG, "oss_url :[%s]", oss_url);

    time_t now = time(NULL);
    struct tm *tm_info = gmtime(&now);
    strftime(gmt_time, sizeof(gmt_time), "%a, %d %b %Y %H:%M:%S GMT", tm_info);

    char *authorization = generate_authorization_upload(gmt_time, oss_path, oss_endpoint, bucket_name, access_key, access_securty, sts_token);
    if(authorization == NULL)
    {
        goto generate_authorization_failed;
    }



    esp_http_client_config_t config = {
        .url = oss_url,
        .method = HTTP_METHOD_PUT,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .event_handler = NULL,
        .buffer_size_tx = 1536,  // 增加缓冲区大小
    };
	
    client = esp_http_client_init(&config);
    if(client == NULL)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_init error", __FILE__, __LINE__);
        goto client_init_failed;
    }

    esp_log_level_set("HTTP_CLIENT", ESP_LOG_VERBOSE);
    ret=esp_http_client_set_header(client, "Content-Type", "application/octet-stream");
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }

    ret=esp_http_client_set_header(client, "Date", gmt_time);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }


    ret=esp_http_client_set_header(client, "Authorization", authorization);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }

    if (sts_token != NULL) {

        ret = esp_http_client_set_header(client, "x-oss-security-token", sts_token);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
            goto buffer_alloc_error;
        }
    }


	char *file_data = (char *)malloc(file_size);
    if(file_data == NULL)
    {
        ESP_LOGE(TAG, "%s(%d) fopen error %s", __FILE__, __LINE__,
                    aos_strerror(errno, err_msg, AOS_ERRNO_MSG_MAX));
        goto buffer_alloc_error;
    }
    memset(file_data,'\0',file_size);

    ret=esp_http_client_set_post_field(client, file_data, file_size);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) mbedtls_base64_encode error %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }   

    fread(file_data, 1, file_size, file);
    ret = esp_http_client_perform(client);
    ESP_LOGI(TAG, "HTTP Status = %d, content_length = %d",
                     (int)esp_http_client_get_status_code(client),
                     (int)esp_http_client_get_content_length(client));
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "%s(%d) HTTP PUT failed: %s", __FILE__, __LINE__, esp_err_to_name(ret));
        goto buffer_alloc_error;
    }

	free(file_data);


buffer_alloc_error:
    esp_http_client_cleanup(client);    
client_init_failed:
    free(authorization);
generate_authorization_failed:
    fclose(file);
    return ret;
}

esp_err_t oss_http_event_handle(esp_http_client_event_t *evt)
{
    static FILE *file = NULL;

    switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            if (file) {
                fclose(file);
                file = NULL;
            }
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER");
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            if (!file) {
                file = fopen((const char *)evt->user_data, "wb");
                if (!file) {
                    ESP_LOGE(TAG, "Failed to open file for writing");
                    return ESP_FAIL;
                }
            }
            if (!esp_http_client_is_chunked_response(evt->client)) {
                fwrite(evt->data, 1, evt->data_len, file);
            }
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            if (file) {
                fclose(file);
                file = NULL;
            }
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
            if (file) {
                fclose(file);
                file = NULL;
            }
            break;
        default:
            break;
    }
    return ESP_OK;
}

esp_err_t oss_download(const char *file_path, 
                const char *oss_endpoint, 
                const char *bucket_name,
                const char *bucket_dir, 
                const char *access_key, 
                const char *access_securty,
                const char *sts_token)
{
    esp_err_t ret = ESP_OK;
    esp_http_client_handle_t client;
    char oss_url[OSS_URL_LEN] = {0};
    char gmt_time[64] = {0};
    char oss_path[OSS_DIR_LEN] = {0};
    char err_msg[AOS_ERRNO_MSG_MAX] = {0};


    const char *filename = strrchr(file_path, '/');
    if (filename) {
        filename = filename + 1;
    } else {
        filename = file_path;
    }
    
    if(bucket_dir!=NULL)
    {
        snprintf(oss_path,sizeof(oss_path),"%s/%s",bucket_dir, filename);
    }
    else
    {
        memcpy(oss_path,filename,sizeof(oss_path));
    }

    snprintf(oss_url, sizeof(oss_url), "https://%s.%s/%s",bucket_name, oss_endpoint, oss_path);
    ESP_LOGI(TAG,  "oss_endpoint:[%s]", oss_endpoint);
    ESP_LOGI(TAG, "oss_url :[%s]", oss_url);

    time_t now = time(NULL);
    struct tm *tm_info = gmtime(&now);
    strftime(gmt_time, sizeof(gmt_time), "%a, %d %b %Y %H:%M:%S GMT", tm_info);

    char *authorization = generate_authorization_download(gmt_time, oss_path, oss_endpoint, bucket_name, access_key, access_securty, sts_token);
    if(authorization == NULL)
    {
        return ESP_FAIL;
    }


    esp_http_client_config_t config = {
        .url = oss_url,
        .method = HTTP_METHOD_GET,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .event_handler = oss_http_event_handle,
        .user_data = (void *)file_path,
        .buffer_size_tx = 1536,  // 增加缓冲区大小
    };
	
    client = esp_http_client_init(&config);
    if(client == NULL)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_init error", __FILE__, __LINE__);
        return ESP_FAIL;
    }

    esp_log_level_set("HTTP_CLIENT", ESP_LOG_VERBOSE);

    ret=esp_http_client_set_header(client, "Date", gmt_time);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        return ret;
    }


    ret=esp_http_client_set_header(client, "Authorization", authorization);
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
        return ret;
    }

    if (sts_token != NULL) {

        ret = esp_http_client_set_header(client, "x-oss-security-token", sts_token);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "%s(%d) esp_http_client_set_header %s", __FILE__, __LINE__, esp_err_to_name(ret));
            return ret;
        }
    }

    ret = esp_http_client_perform(client);
    ESP_LOGI(TAG, "HTTP Status = %d, content_length = %d",
                     (int)esp_http_client_get_status_code(client),
                     (int)esp_http_client_get_content_length(client));
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "%s(%d) HTTP PUT failed: %s", __FILE__, __LINE__, esp_err_to_name(ret));
    }

    esp_http_client_cleanup(client);    
    return ret;
}


void oss_task(void *pvParameters)
{
    const char *file_path="/spiffs/gyro_error.wav";
    const char *file_path2="/spiffs/test_door.mp3";
    const char *oss_endpoint="oss-cn-shenzhen.aliyuncs.com";
    const char *bucket_name="smart-fridge";
    const char *bucket_dir="test";
    const char *access_key="LTAI5t6W9tdUSLtvCGd5exzs";
    const char *access_securty="dTGFitFT72eulZlOzlL4aMwD8TYv8k";
    const char *sts_token=NULL;

    //将file_path文件上传到oss服务器的bucket_dir目录下 
    esp_err_t ret = oss_upload(file_path, oss_endpoint, bucket_name, bucket_dir, access_key, access_securty, sts_token);
    printf("oss_upload file_path = %s ret = %d\n", file_path, ret);
    //将file_path文件上传到oss服务器的bucket_name目录下
    ret = oss_upload(file_path, oss_endpoint, bucket_name, NULL, access_key, access_securty, sts_token);
    printf("oss_upload file_path = %s ret = %d\n", file_path, ret);   
    ret = oss_download(file_path2, oss_endpoint, bucket_name, bucket_dir, access_key, access_securty, sts_token);
    printf("oss_download file_path2 = %s ret = %d\n", file_path2, ret);
    ret = oss_download(file_path, oss_endpoint, bucket_name, NULL, access_key, access_securty, sts_token);
    printf("oss_download file_path = %s ret = %d\n", file_path, ret);

    vTaskDelete(NULL);
}
void oss_task_start()
{
    xTaskCreate(oss_task, "oss_task", 8192, NULL, 12, NULL);
}